﻿using System.Reflection.Emit;
using System;
using System.Linq;
using ProxyViewModel.Commands;
using System.Collections.Generic;
using System.Reflection;

namespace ProxyViewModel.Emitors {
	public class CommandsContainerEmitor : EmitorBase {
		public CommandsContainerEmitor(TypeBuilder typeBuilder, Type operatable, IDictionary<string, object> parameters) :
			base(typeBuilder, operatable, parameters) { }

		protected override void EmitContent() {
			if (Operatable.GetInterfaces().Contains(typeof(IProxyCommandsContainer))) {
				// todo: replace with custom exception
				throw new Exception("IProxyCommandsContainer cannot be implemented directly by a ViewModel");
			}

			TypeBuilder.AddInterfaceImplementation(typeof(IProxyCommandsContainer));
			FieldBuilder builder = DefineField();
			DefineProperty(builder);
			DefineMethodToInitializeField(builder);
		}

		private FieldBuilder DefineField() {
			FieldBuilder builder = TypeBuilder.DefineField("_commands", typeof(List<ProxyCommand>), FieldAttributes.Private);
			return builder;
		}

		private void DefineProperty(FieldBuilder field) {
			PropertyBuilder property = TypeBuilder.DefineProperty("Commands", PropertyAttributes.HasDefault, typeof(IList<ProxyCommand>),
				new Type[] { typeof(IList<ProxyCommand>) });

			MethodBuilder getter = TypeBuilder.DefineMethod("get_Commands", MethodAttributes.Public | MethodAttributes.NewSlot | MethodAttributes.Virtual |
				MethodAttributes.SpecialName | MethodAttributes.HideBySig, typeof(IList<ProxyCommand>), Type.EmptyTypes);

			ILGenerator il = getter.GetILGenerator();

			il.DeclareLocal(typeof(IList<ProxyCommand>));
			il.Emit(OpCodes.Nop);
			il.Emit(OpCodes.Ldarg_0);
			il.Emit(OpCodes.Ldfld, field);
			il.Emit(OpCodes.Stloc_0);
			Label lbl = il.DefineLabel();
			il.Emit(OpCodes.Br_S, lbl);
			il.MarkLabel(lbl);
			il.Emit(OpCodes.Ldloc_0);
			il.Emit(OpCodes.Ret);

			property.SetGetMethod(getter);
		}

		private void DefineMethodToInitializeField(FieldBuilder fieldBuilder) {
			MethodBuilder info = TypeBuilder.DefineMethod("InitializeCommandsContainer", MethodAttributes.Private);
			ILGenerator il = info.GetILGenerator();

			il.Emit(OpCodes.Ldarg_0);
			il.Emit(OpCodes.Newobj, typeof(List<ProxyCommand>).GetConstructor(new Type[]{}));
			il.Emit(OpCodes.Stfld, fieldBuilder);
			il.Emit(OpCodes.Ret);

			CallMethodOnConstruction(info);
		}
	}
}
